import threading
import socket  # 导入socket模块  socket模块用于创建套接字
import struct  # 导入struct模块  struct模块用于处理字节流
import time  # 导入time模块
from concurrent.futures import ThreadPoolExecutor, as_completed  # 导入线程池模块  线程池模块用于管理线程池
from tqdm import tqdm  # 导入进度条模块
from ..utils.network import (  # 导入网络相关函数
    parse_target_range,  # 解析目标范围
    parse_ports,  # 解析端口范围
    is_host_alive,  # 检测主机是否存活
    create_syn_packet,  # 创建SYN包
    send_udp_probe  # 发送UDP探测包
)
from ..utils.logger import get_logger  # 导入日志记录器
from ..utils.reporter import Reporter  # 导入报告生成器
from ..utils.service_detection import detect_service  # 导入服务检测函数
from ..config.ports import ALL_COMMON_PORTS  # 导入常用端口列表


class Scanner:
    """端口扫描器主类
    负责执行端口扫描的功能，包括：
    - 目标解析和验证
    - 多线程端口扫描
    - 服务检测
    - 结果收集和报告生成
    """

    def __init__(self, args):
        """初始化扫描器
        
        Args:
            args: 命令行参数对象，包含扫描配置信息
        """
        self.args = args  # 保存命令行参数
        self.logger = get_logger()  # 初始化日志记录器
        self.reporter = Reporter(args.output)  # 初始化报告生成器
        self.results = []  # 存储扫描结果
        self.lock = threading.Lock()  # 线程锁，用于保护共享资源
        self.start_time = None  # 扫描开始时间
        self.end_time = None  # 扫描结束时间
        self.total_tasks = 0  # 总任务数
        self.completed_tasks = 0  # 已完成任务数

    def start(self):
        """启动扫描过程
        
        主要步骤：
        1. 解析目标和端口
        2. 执行主机存活检测（可选）
        3. 启动多线程端口扫描
        4. 生成扫描报告
        """
        self.logger.info("正在初始化扫描...")
        self.start_time = time.time()

        # 解析目标范围（支持单个IP、IP范围、CIDR）
        self.logger.info("正在解析目标范围...")
        targets = parse_target_range(self.args.target)
        if not targets:
            self.logger.error("未指定有效的目标")
            return
        self.logger.info(f"发现 {len(targets)} 个目标待扫描")

        # 解析端口范围（支持单个端口、端口列表、端口范围）
        self.logger.info("正在解析端口范围...")
        if not self.args.ports:
            self.logger.info("未指定端口，将扫描所有常见端口")
            ports = ALL_COMMON_PORTS
        else:
            ports = parse_ports(self.args.ports, self.args.scan_type)

        if not ports:
            self.logger.error("无有效的端口")
            return
        self.logger.info(f"将扫描 {len(ports)} 个端口")

        # 计算总任务数（目标数 × 端口数）
        self.total_tasks = len(targets) * len(ports)

        # 如果未禁用主机存活检测，则执行ping扫描
        if not self.args.no_ping:
            self.logger.info("正在进行主机存活检测...")
            with tqdm(total=len(targets), desc="主机存活检测", unit="主机") as pbar:
                live_targets = []
                for ip in targets:
                    if is_host_alive(ip):
                        live_targets.append(ip)
                        self.logger.debug(f"主机 {ip} 存活")
                    pbar.update(1)
                targets = live_targets

            if not targets:
                self.logger.warning("未发现存活主机")
                return
            self.logger.info(f"发现 {len(targets)} 个存活主机")

        # 开始多线程端口扫描
        self.logger.info(f"开始对 {len(targets)} 个主机进行端口扫描...")
        scan_tasks = []

        # 创建进度条
        with tqdm(total=self.total_tasks, desc="端口扫描进度", unit="端口") as self.pbar:
            # 使用线程池执行扫描任务
            with ThreadPoolExecutor(max_workers=self.args.threads) as executor:
                # 为每个目标的每个端口创建扫描任务
                for target in targets:
                    for port in ports:
                        task = executor.submit(self._scan_target, target, port)
                        scan_tasks.append(task)

                # 等待所有任务完成并更新进度条
                for task in as_completed(scan_tasks):
                    self.pbar.update(1)

        # 计算扫描统计信息
        self.end_time = time.time()
        duration = round(self.end_time - self.start_time, 2)
        open_ports = len([r for r in self.results if r['status'] == 'open'])

        # 输出扫描摘要
        self.logger.info("\n扫描摘要:")
        self.logger.info(f"扫描用时: {duration} 秒")
        self.logger.info(f"已扫描主机数: {len(targets)}")
        self.logger.info(f"每个主机扫描端口数: {len(ports)}")
        self.logger.info(f"总计扫描端口数: {self.total_tasks}")
        self.logger.info(f"发现开放端口数: {open_ports}")

        # 生成扫描报告
        self.logger.info("\n正在生成报告...")
        self.reporter.generate_report(self.results, {
            'start_time': self.start_time,
            'end_time': self.end_time,
            'total_hosts': len(targets),
            'total_ports': len(ports)
        })

    def _scan_target(self, target, port):
        """扫描单个目标端口
        
        Args:
            target: 目标IP地址
            port: 要扫描的端口号
            
        执行步骤：
        1. 根据扫描类型选择扫描方法
        2. 对开放端口执行服务检测
        3. 记录扫描结果
        """
        try:
            # 初始化结果字典
            result = {
                'target': target,
                'port': port,
                'status': 'closed',  # 默认端口状态为关闭
                'service': None,
                'latency': None,
                'service_info': None
            }

            start_time = time.time()

            # 根据扫描类型选择相应的扫描方法
            if self.args.scan_type == 'syn':
                try:
                    # 尝试SYN扫描（需要管理员权限）
                    status = self._syn_scan(target, port)
                except PermissionError:
                    # 权限不足时降级为TCP扫描
                    self.logger.warning("权限不足，无法执行SYN扫描，正在切换为TCP扫描")
                    self.args.scan_type = 'tcp'
                    status = self._tcp_scan(target, port)
            elif self.args.scan_type == 'tcp':
                status = self._tcp_scan(target, port)
            else:  # UDP扫描
                status = self._udp_scan(target, port)

            # 记录端口状态和响应时间
            result['status'] = status
            result['latency'] = round((time.time() - start_time) * 1000, 2)

            # 对开放的端口执行服务检测
            if status == 'open':
                with self.lock:
                    self.logger.info(f"发现开放端口 {target}:{port}")
                # 检测端口上运行的服务
                service_info = detect_service(target, port, self.args.timeout)
                result['service'] = service_info['name']
                result['service_info'] = service_info

                # 记录服务信息
                if service_info['name'] != 'unknown':
                    with self.lock:
                        self.logger.info(f"端口 {target}:{port} 运行的服务: {service_info['name']}")
                        if service_info['product']:
                            self.logger.info(f"  产品: {service_info['product']} {service_info['version'] or ''}")
                        if service_info['http_title']:
                            self.logger.info(f"  网页标题: {service_info['http_title']}")

            # 将扫描结果添加到结果列表
            with self.lock:
                self.results.append(result)

        except Exception as e:
            self.logger.error(f"扫描 {target}:{port} 时发生错误 - {str(e)}")

    def _syn_scan(self, target, port):
        """执行SYN扫描（半开放扫描）
        
        Args:
            target: 目标IP地址
            port: 要扫描的端口号
            
        Returns:
            str: 端口状态（'open'/'closed'/'filtered'）
        
        原理：
        1. 发送SYN包
        2. 如果收到SYN+ACK响应，说明端口开放
        3. 如果收到RST响应，说明端口关闭
        4. 如果超时未响应，说明端口被过滤
        """
        # 创建原始套接字
        sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
        sock.settimeout(self.args.timeout)

        # 发送SYN包
        packet = create_syn_packet(target, port)
        sock.sendto(packet, (target, 0))

        try:
            # 等待响应
            data = sock.recv(1024)
            if data:
                # 检查是否收到SYN+ACK
                tcp_header = data[20:40]
                flags = struct.unpack('!B', tcp_header[13:14])[0]
                if flags & 0x12:  # SYN+ACK标志
                    return 'open'
            return 'closed'
        except socket.timeout:
            return 'filtered'
        finally:
            sock.close()

    def _tcp_scan(self, target, port):
        """执行TCP连接扫描
        
        Args:
            target: 目标IP地址
            port: 要扫描的端口号
            
        Returns:
            str: 端口状态（'open'/'closed'/'filtered'）
        
        原理：
        尝试建立完整的TCP连接，根据连接结果判断端口状态
        """
        # 创建TCP套接字
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(self.args.timeout)

        try:
            # 尝试连接
            result = sock.connect_ex((target, port))
            if result == 0:
                return 'open'
            return 'closed'
        except socket.timeout:
            return 'filtered'
        finally:
            sock.close()

    def _udp_scan(self, target, port):
        """执行UDP扫描
        
        Args:
            target: 目标IP地址
            port: 要扫描的端口号
            
        Returns:
            str: 端口状态（'open'/'closed'）
            
        原理：
        发送UDP探测包，根据响应判断端口状态
        由于UDP的无连接特性，此方法可能不太可靠
        """
        # 多次尝试UDP探测
        for _ in range(self.args.udp_retries):
            if send_udp_probe(target, port, self.args.timeout):
                return 'open'
        return 'closed'
